import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig as _RequestConfig, AxiosResponse as _Response, InternalAxiosRequestConfig as _InternalRequestConfig } from 'axios';
import url from 'url';
import qs from 'qs';
import $Tool from './basics';
import type { notifyOptions } from './basics';

export interface AxiosRequestConfig<D = any> extends _RequestConfig<D> {
  cacheExpire?: string;
}

export interface InternalAxiosRequestConfig<D = any> extends _InternalRequestConfig<D> {
  cacheExpire?: string;
  commands?: string[];
}

export interface AxiosResponse<T = any, D = any> extends _Response<T, D> {
  config: InternalAxiosRequestConfig<D>;
}

export interface _AxiosResponse<T = any> extends Record<string, any> {
  code: number;
  message: string;
  data: T;
}

export interface _AxiosInstance extends AxiosInstance {
  request<T = any, R = _AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
  get<T = any, R = _AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
  delete<T = any, R = _AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
  head<T = any, R = _AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
  options<T = any, R = _AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
  post<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  put<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  patch<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  postForm<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  putForm<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  patchForm<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
}

export interface downloadOptions {
  filename?: string;
  type?: 'get' | 'post';
  url: string;
  data?: any;
  config?: AxiosRequestConfig<any>;
  // onDownloadProgress?: (progressEvent: any) => void;
}

export interface API_CONFIG {
  baseURL?: string;
  contentType: string;
  requestTimeout: `${number}s`;
  successCode: (number | string)[];
  statusName: string;
  messageName: string;
  tokenName: string;
  cacheExpire: string;
}

export default class {
  protected CONFIG: API_CONFIG = {
    // 默认的接口地址，开发环境和生产环境都会走/vab-mock-server
    // 正式项目可以选择自己配置成需要的接口地址，如"https://api.xxx.com"
    // 问号后边代表开发环境，冒号后边代表生产环境
    // 如果不需要测试环境解除以下注释即可
    baseURL: undefined,
    // 配后端数据的接收方式application/json;charset=UTF-8 或 application/x-www-form-urlencoded;charset=UTF-8
    contentType: 'application/json;charset=UTF-8',
    // 最长请求时间
    requestTimeout: '60s',
    // 操作正常Code数组
    successCode: [200, 201],
    // 数据状态的字段名称
    statusName: 'code',
    // 状态信息的字段名称
    messageName: 'message',
    // tokenName 配置 headers 自定义 token 名称(key)
    tokenName: 'Authorization',
    // cacheExpire -- 配置缓存有效期，默认3天
    cacheExpire: '3d'
  };
  protected CODE_MESSAGE: { [key: string]: string } = {
    200: '服务器成功返回请求数据',
    201: '新建或修改数据成功',
    202: '一个请求已经进入后台排队(异步任务)',
    204: '删除数据成功',
    400: '发出信息有误',
    401: '用户没有权限(令牌失效、用户名、密码错误、登录过期)',
    402: '令牌过期',
    403: '用户得到授权，但是访问是被禁止的',
    404: '访问资源不存在',
    406: '请求格式不可得',
    410: '请求资源被永久删除，且不会被看到',
    500: '服务器发生错误',
    502: '网关错误',
    503: '服务不可用，服务器暂时过载或维护',
    504: '网关超时'
  };
  /**
   * API URL 命令说明 /api/example#msg#cache
   * #loading -- 请求过程中显示加载动画
   * #cache -- 启用接口缓存 | 修改有效期 { cacheExpire: '5m' }
   * #cache_add -- 更新接口缓存(仅存储)
   * #msg -- 请求成功状态也自动消息提示
   */
  protected getCommand(urlStr: string | undefined): string[] {
    if (!urlStr) return [];
    const reg = /(#[\w]+)/g;
    return urlStr.match(reg) || [];
  }
  protected onPrintResponse(response: AxiosResponse) {
    // ---
  }
  protected disposeLoading(isLoading: boolean) {
    // $Tool.baseLoading(isLoading);
  }
  protected disposeMessage(options: notifyOptions) {
    // if (options.title) {
    //   $Tool.notify(options);
    // } else {
    //   $Tool.message(options);
    // }
  }
  protected getToken(): string | null | undefined {
    return undefined;
  }
  protected getApiMd5(config: InternalAxiosRequestConfig) {
    const { CONFIG } = this;
    let str_url = `${config.method?.toUpperCase()} ${config.url}`;
    const paramsStr = qs.stringify(config.params);
    if (paramsStr) str_url = str_url.replace(/(?=(#|$))/, `?${paramsStr}`);
    const _strs: string[] = [];
    _strs.push(str_url);
    // _strs.push(`Content-Type: ${config.headers?.['Content-Type']}`);
    _strs.push(`${CONFIG.tokenName}: ${config.headers?.[CONFIG.tokenName]}`);
    let configData: undefined | Record<string, any> = config.data;
    if ($Tool.type(configData) === 'formdata') {
      const _configData: Record<string, any> = {};
      (configData as FormData).forEach((value, key) => {
        _configData[key] = value;
      });
      configData = _configData;
    }
    if (configData) {
      const data = typeof configData === 'object' ? JSON.stringify(configData) : configData;
      _strs.push(data);
    }
    return $Tool.md5(_strs.join('\n'));
  }
  protected addErrorLog(errorInfo: { message: string; stack: any; isRequest: boolean }) {
    // ---
  }
  protected addCacheRes(response: AxiosResponse) {
    // ---
  }
  protected getCacheRes(config: InternalAxiosRequestConfig) {
    // ---
  }
  // axios 请求拦截器配置 (额外的)
  protected requestConfExtra(config: InternalAxiosRequestConfig) {
    // const msgId = $Tool.uuid.v4();
    // config.headers['X-Api-MsgId'] = msgId;
    // config.params = { _: +new Date(), ...config.params };
    // config.onDownloadProgress = (progressEvent: any) => {
    //   console.log(progressEvent);
    //   const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    //   console.log(`下载进度：${percentCompleted}%`);
    // };
    // config.onUploadProgress = (progressEvent: any) => {
    //   const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    //   console.log(`上传进度：${percentCompleted}%`);
    // };
  }
  // axios 响应拦截器配置 (额外的)
  protected disposeResExtra(code: number | string, response: AxiosResponse) {
    // 处理失败请求 - 错误内容：{ code: 1, msg: '非法参数' }
    switch (code) {
      case 401:
        // 退出登陆状态
        // const userStore = useUserStore();
        // userStore.logout();
        break;
      case 402:
        // 令牌失效，重新登陆
        // return await tryRefreshToken(config); // 暂不支持
        break;
      case 403:
        // 没有权限
        // router.push({ path: '/403' }).then(() => {});
        break;
    }
    return Promise.reject(response);
  }
  instance: _AxiosInstance;
  constructor(_CONFIG?: Partial<API_CONFIG>) {
    const CONFIG = { ...this.CONFIG, ..._CONFIG };
    this.CONFIG = CONFIG;
    /**
     * @description axios初始化
     */
    const instance: _AxiosInstance = axios.create({
      baseURL: CONFIG.baseURL,
      timeout: $Tool.ms(CONFIG.requestTimeout),
      headers: {
        'Content-Type': CONFIG.contentType
      }
    });
    this.instance = instance;
    /**
     * @description axios请求拦截器 - 请求拦截器 / 异常处理
     */
    instance.interceptors.request.use(this.requestConf, (error) => {
      return Promise.reject(error);
    });
    /**
     * @description axios响应拦截器
     */
    instance.interceptors.response.use(
      (response) => this.handleData(response),
      (error) => {
        if (axios.isCancel(error)) {
          const config = (error as any)?.config as InternalAxiosRequestConfig;
          const _commands = config?.commands || [];
          if (_commands.includes('#loading')) this.disposeLoading(true);
          const res = error.message as any;
          if (res && typeof res === 'object') {
            const mesObj: notifyOptions = { type: 'success', title: res.title, message: res[CONFIG.messageName] };
            if (_commands.includes('#msg')) this.disposeMessage(mesObj);
          }
          return Promise.resolve(res);
        }
        this.disposeLoading(false);
        const { response } = error;
        if (response) return this.handleData(response);
        // 服务器未响应
        const errMsg = '连接服务器失败，请联系客服';
        this.disposeMessage({ type: 'error', message: errMsg });
        return Promise.reject(error);
      }
    );
  }

  // protected refreshToking = false;
  // protected requests: (() => void)[] = [];
  // /**
  //  * 刷新刷新令牌
  //  * @param config 过期请求配置
  //  * @returns {any} 返回结果
  //  */
  // protected async tryRefreshToken(config: InternalAxiosRequestConfig<any>) {
  //   if (!this.refreshToking) {
  //     this.refreshToking = true;
  //     try {
  //       const {
  //         data: { token }
  //       }: any = await refreshToken();
  //       if (token) {
  //         const { setToken } = useUserStore();
  //         setToken(token);
  //         // 已经刷新了token，将所有队列中的请求进行重试
  //         this.requests.forEach((cb: any) => cb(token));
  //         this.requests = [];
  //         return instance(requestConf(config));
  //       }
  //     } catch (error) {
  //       console.error('refreshToken error =>', error);
  //       router.push({ path: '/login', replace: true }).then(() => {});
  //     } finally {
  //       this.refreshToking = false;
  //     }
  //   } else {
  //     return new Promise((resolve) => {
  //       // 将resolve放进队列，用一个函数形式来保存，等token刷新后直接执行
  //       this.requests.push(() => {
  //         resolve(instance(requestConf(config)));
  //       });
  //     });
  //   }
  // }

  /**
   * axios请求拦截器配置
   * @param config
   * @returns {any}
   */
  protected requestConf = async (config: InternalAxiosRequestConfig<any>) => {
    const { CONFIG } = this;
    config.commands = this.getCommand(config.url);
    if (config.url) {
      const urlParse = url.parse(config.url, true);
      if (urlParse.search) {
        config.url = config.url.replace(urlParse.search, '');
        config.params = { ...urlParse.query, ...config.params };
      }
      if (urlParse.hash) config.url = config.url.replace(urlParse.hash, '');
    }
    const token = await this.getToken();
    const _commands = config?.commands || [];
    if (_commands.includes('#loading')) this.disposeLoading(true);
    // 可根据tokenName配置随意自定义headers
    if (token) config.headers[CONFIG.tokenName] = token;
    if (config.data && typeof config.headers['Content-Type'] === 'string' && config.headers['Content-Type'].includes('application/x-www-form-urlencoded')) config.data = qs.stringify(config.data);
    await this.requestConfExtra(config); // 额外的请求拦截器配置
    if (_commands.includes('#cache')) {
      try {
        await this.getCacheRes?.(config); // 使用缓存数据
      } catch (error) {
        console.warn('[getCacheRes]', error);
      }
    }
    return Promise.resolve(config);
  };

  /**
   * 处理响应结果
   * @param code 状态码
   * @param response 响应体
   * @returns {any} 返回结果
   */
  protected disposeRes(code: number | string, response: AxiosResponse) {
    const { CONFIG } = this;
    const { config, data: res } = response;
    const _commands = config?.commands || [];
    // 业务层级错误处理，以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理
    // 处理成功请求 - 正确内容：{ code: 200, data: {  }, msg: '操作正常' }
    if (CONFIG.successCode.includes(code)) {
      if (res && typeof res === 'object') {
        const mesObj: notifyOptions = { type: 'success', title: res.title, message: res[CONFIG.messageName] };
        if (_commands.includes('#msg')) this.disposeMessage(mesObj);
      }
      return Promise.resolve(res);
    }
    return this.disposeResExtra(code, response);
  }

  /**
   * axios响应拦截器配置
   * @param config 请求配置
   * @param data response数据
   * @param status HTTP status
   * @param statusText HTTP status text
   * @returns {Promise<*|*>}
   */
  protected async handleData(response: AxiosResponse) {
    this.onPrintResponse(response);
    const { config, data, status, statusText } = response;
    const { CONFIG, CODE_MESSAGE } = this;
    const _commands = config?.commands || [];
    let code = status;
    if (_commands.includes('#loading')) this.disposeLoading(false);
    if (data && typeof data === 'object') {
      if (data[CONFIG.statusName] !== undefined) {
        code = Number(data[CONFIG.statusName]) || data[CONFIG.statusName];
      }
    }
    // 若data.code存在，覆盖默认code
    // 若code属于操作正常code，则status修改为200
    // if (CONFIG.successCode.includes(data[CONFIG.statusName])) code = 200;
    try {
      const result = await this.disposeRes(code, response);
      if (_commands.some((val) => ['#cache', '#cache_add'].includes(val))) {
        try {
          await this.addCacheRes?.(response); // 缓存 Api 数据
        } catch (error) {
          console.warn('[addCacheRes]', error);
        }
      }
      return Promise.resolve(result);
    } catch (error) {
      // 异常处理
      // 若data.message存在，覆盖默认提醒消息
      let errMsg = `[${code}] ${statusText}`;
      if (data && data[CONFIG.messageName]) {
        errMsg = data[CONFIG.messageName];
      } else if (CODE_MESSAGE[code]) {
        errMsg = CODE_MESSAGE[code];
      }
      // 是否显示高亮错误(与errorHandler钩子触发逻辑一致)
      this.disposeMessage({ type: 'error', message: errMsg });
      this.addErrorLog({ message: errMsg, stack: data, isRequest: true });
      return Promise.reject(data);
    }
  }

  public request<T = any, R = _AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.request(config);
  }
  public get<T = any, R = _AxiosResponse<T>, D = any>(url: string, params?: any, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.get(url, { params, ...config });
  }
  public post<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.post(url, data, config);
  }
  public put<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.put(url, data, config);
  }
  public delete<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.delete(url, { data, ...config });
  }
  public head<T = any, R = _AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.head(url, config);
  }
  public options<T = any, R = _AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.options(url, config);
  }
  public patch<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.patch(url, data, config);
  }
  public postForm<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.postForm(url, data, config);
  }
  public putForm<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.putForm(url, data, config);
  }
  public patchForm<T = any, R = _AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R> {
    return this.instance.patchForm(url, data, config);
  }
  public async download(options: downloadOptions) {
    options = {
      type: 'get',
      ...options
    };
    const blob: Blob = await this[options.type!](options.url, options.data, {
      ...options.config,
      responseType: 'blob'
    });
    const reg = /([\w]+)$/;
    let suffix = blob.type.match(reg)?.[1] || '';
    suffix = suffix && `.${suffix}`;
    if (options.filename) {
      let filename = options.filename;
      if (!filename.includes('.')) filename = `${filename}${suffix}`;
      const downloadUrl = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = downloadUrl;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(downloadUrl);
    }
    return Promise.resolve(blob);
  }
}
